package net.fishear.web.t5.jquery.base;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import net.fishear.data.generic.entities.EntityI;
import net.fishear.data.generic.query.QueryConstraints;
import net.fishear.data.generic.query.QueryFactory;
import net.fishear.data.generic.query.restrictions.Restrictions;
import net.fishear.data.generic.services.ServiceI;
import net.fishear.utils.Texts;
import net.fishear.web.t5.base.ComponentBase;

import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.AfterRender;
import org.apache.tapestry5.annotations.Cached;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

// http://salman-w.googlecode.com/svn/trunk/jquery-ui-autocomplete/using-label-value-pairs.html

/**
 * Base class for autocomplete entity selection.
 * 
 * Successor must implement usual things - {@link #getService()} method and {@link #getVisibleText(EntityI)}.
 * 
 * Also {@link #modifyConstraints(QueryConstraints)} is available to restrict / extend selected data.
 * 
 * @author raterwork
 *
 *
 *
 * @param <T>
 */
@SupportsInformalParameters
@Import(library="AutocompleteBase.js")
public abstract class 
	AutocompleteBase<T extends EntityI<?>>
extends
	ComponentBase
{
	
	@Parameter
	private T value;
	
	private String visibleText;

	@Parameter(value="25", allowNull=false, required=false)
	private int rowsDisplayed;
	
	@Inject
	Request request;

	String selectedId;
	
	@Inject
	JavaScriptSupport jsup;

	/**
	 * @return data providing service 
	 */
	protected abstract ServiceI<T> getService();
	
	/**
	 * @param en the entity the text is constructed from
	 * @return constructs text that will be visible in the dropdown and text field
	 */
	protected abstract String getVisibleText(T en);

	/**
	 * @return comma separated list of field names that will be searched over
	 */
	protected abstract String getFieldNames();

	/**
	 * allows caller to change autogenerated constraints.
	 * 
	 * @param qc
	 */
	protected void modifyConstraints(QueryConstraints qc) {
	}

	@SetupRender
	void before() {
		
		if(value != null && !value.isNew()) {
			visibleText = getVisibleText(value);
			selectedId = value.getIdString();
		} else if(selectedId != null) {
			value = readValue(selectedId);
		}
	}

	private T readValue(String selectedId) {
		T en;
		(en = getService().newEntityInstance()).setIdString(selectedId);
		return getService().read(en.getId());
	}

	@AfterRender
	void afterrender(MarkupWriter wr) {
		Element el = wr.getElement().getElementByAttributeValue("type", "text");
		if(el != null) {
			for(String s : crsc.getInformalParameterNames()) {
				el.attribute(s, crsc.getInformalParameter(s, String.class));
			}
		}
	}

	/**
	 * @param value the value to set
	 */
	public void setValue(T value) {
		this.value = value;
	}

    @OnEvent("provideCompletions")
    public List<JSONObject> autoComplete(String start)
    {
    	QueryConstraints qc = QueryFactory.create();

    	qc.results().setResultsPerPage(rowsDisplayed);
    	
    	Restrictions restictions = Restrictions.searchAny(start, getFieldNames().split(","));
    	
    	modifyConstraints(qc);

    	List<JSONObject> res = new ArrayList<JSONObject>();

    	for(T fld : list(qc, restictions)) {
    		res.add(new JSONObject("label", getVisibleText(fld), "value", fld.getIdString()));
    	}
    	return res;
    }

	/**
	 * Lists all available data. 
	 * Designed to allow successors to change behaviour.
	 * 
	 * @param qc the constraint !! WITHOUT !! restriction set that would limit data to the text.
	 * @param textRestriction 	the text restriction prepared for to be added to the query constraints (but the 'qc' DOES NOR CONTAIN it). 
	 * 							It is based on current text passed from client and field  set returned from successor.
	 * t from the successot.
	 * @return data list
	 */
	protected List<T> list(QueryConstraints qc, Restrictions textRestriction) {
    	qc.add(textRestriction);
		return getService().list(qc);
	}

	/**
	 * @return the typedText
	 */
	public String getTypedText() {
		return visibleText;
	}

	/**
	 * @param typedText the typedText to set
	 */
	public void setTypedText(String typedText) {
		if(typedText == null || typedText.length() == 0) {
			selectedId = null;
			value = null;
		}
	}

	public T getValue() {
		return value;
	}
	
	void onTypedText() {
		log.debug("FieldDefAutocompl.onTypedText()" + request.getParameter("param"));
		for (String s : request.getParameterNames()) {
			log.debug(s + " - " + request.getParameter(s));
		}
		setTypedText(request.getParameter("param"));
	}

	@Cached
	public String getClientIdSuffix() {
		return jsup.allocateClientId("");
	}

	/**
	 * @return the selectedId
	 */
	public String getSelectedId() {
		return selectedId;
	}

	/**
	 * @param selectedId the selectedId to set
	 */
	public void setSelectedId(String selectedId) {
		this.selectedId = selectedId;
		if(Texts.tos(selectedId).length() > 0) {
			value = readValue(selectedId);
		} else {
			value = null;
		}
	}
}
